/*
 * This is GPL v2 licensed code. See the file LICENSE in the
 * source distribution, or www.gnu.org/copyleft/gpl.html, for
 * more information.
 *
 * Copyright (C) by Håkan Lindqvist 2006, 2007
 */

package PolicyAlgebra.Controller;

import java.util.HashMap;
import javax.xml.parsers.*;
import org.xml.sax.InputSource;
import org.w3c.dom.*;


/** 
 * This class provides an easy means for parsing the plugin
 * configuration and accessing its contents.
 */
public class PluginController {
	private static PluginController pluginControllerSingleton = null;
	private HashMap<String,PluginInfo> pluginInfoHash = null;


	/** 
	 * This class is used with the pluginInfoHash to store information 
	 * about loaded configuration information.
	 */
	private static class PluginInfo {
			public String fileName;
			public String className;

			public PluginInfo(String fileName, String className) {
				this.fileName = fileName;
				this.className = className;
			}
	}


	/** 
	 * Private constructor, sets up the parsing of the configuration.
	 * 
	 * @throws ControllerException If something goes wrong when loading the
	 *         configuration.
	 */
	private PluginController() throws ControllerException {
		// Set up the hash that will cache the plugin information.
		pluginInfoHash = new HashMap<String,PluginInfo>();

		// Load configuration (depends upon the previous hash).
		getPluginConfiguration();
	}


	/** 
	 * Loads the configuration from the configuration file.
	 * 
	 * @throws ControllerException If no filename corresponding to the supplied
	 *         name is found.
	 */
	private void getPluginConfiguration() throws ControllerException {
		Element pluginConfigElement;

		try {
			String pluginConfigurationFile = Controller.getInstance().getInstallationLocation() + "/plugins/plugins.xml";

			DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
			DocumentBuilder parser = factory.newDocumentBuilder();
			Document document = parser.parse(new InputSource(pluginConfigurationFile));

			/* Fetch all the elements of the configuration file */
			pluginConfigElement = document.getDocumentElement();
		} catch (Exception e) {
			throw new ControllerException("Failed to load plugin configuration: " 
					+ e.getMessage());
		}

		// Fetch all <plugin> markups as a list of such nodes.
		NodeList plugins = pluginConfigElement.getElementsByTagName("plugin");

		/* Parse the children of the plugin list */
		for (int i=0; i<plugins.getLength(); i++) {
			// Get a <plugin>
			Element currentElement = (Element) plugins.item(i);

			// Fetch name field and short field
			NodeList nlName = currentElement.getElementsByTagName("name");
			NodeList nlShort = currentElement.getElementsByTagName("short");
			NodeList nlFile = currentElement.getElementsByTagName("file");
			NodeList nlClass = currentElement.getElementsByTagName("class");

			// Sanity checking
			if (nlName.getLength() != 1) {
				throw new ControllerException("the 'plugins/plugins.xml' misses a required <name> field in one of its plugin configuration sections or has too many, please review the configuration");
			}

			// Same again, but for short
			if (nlShort.getLength() != 1) {
				throw new ControllerException("the 'plugins/plugins.xml' misses a required <short> field in one of its plugin configuration sections or has too many, please review the configuration");
			}

			// Yet again, but for file
			if (nlFile.getLength() != 1) {
				throw new ControllerException("the 'plugins/plugins.xml' misses a required <file> field in one of its plugin configuration sections or has too many, please review the configuration");
			}

			// Yet again, but for class
			if (nlClass.getLength() != 1) {
				throw new ControllerException("the 'plugins/plugins.xml' misses a required <class> field in one of its plugin configuration sections or has too many, please review the configuration");
			}

			Element elName = (Element) nlName.item(0);
			Element elShort = (Element) nlShort.item(0);
			Element elFile = (Element) nlFile.item(0);
			Element elClass = (Element) nlClass.item(0);

			Node textName = elName.getFirstChild();
			Node textShort = elShort.getFirstChild();
			Node textFile = elFile.getFirstChild();
			Node textClass = elClass.getFirstChild();

			// Fetch pure string values to use for something useful.
			String pluginName = null;
			String pluginShort = null;
			String fileName = null;
			String className = null;


			// The following should work out fine now.
			if (textName instanceof Text) {
				pluginName = textName.getNodeValue();
			}

			if (textShort instanceof Text) {
				pluginShort = textShort.getNodeValue();
			}

			if (textFile instanceof Text) {
				fileName = textFile.getNodeValue();
			}

			if (textClass instanceof Text) {
				className = textClass.getNodeValue();
			}

			// Bag the information in one neat place.
			PluginInfo info = new PluginInfo(fileName, className);

			// Make it accessable for later.
			pluginInfoHash.put(pluginName, info);
			pluginInfoHash.put(pluginShort, info);
		}
	}


	/** 
	 * Accessor method for the singleton of this controller.
	 * 
	 * @return The single instance of this controller.
	 * @throws ControllerException If something goes wrong while constructing 
	 *         the singleton (ie during the first call to this method).
	 */
	protected static PluginController getInstance() throws ControllerException {
		if (pluginControllerSingleton == null) {
			pluginControllerSingleton = new PluginController();
		}

		return pluginControllerSingleton;
	}


	/** 
	 * Looks up the file name for a plugin.
	 * 
	 * @param pluginName The name to check the configuration for the actual
	 *        actual file name.
	 * @return The name of the file in the installations plugin directory that
	 *         is defined to correspond to the provided name.
	 * @throws ControllerException If no filename corresponding to the supplied
	 *         name is found.
	 */
	protected String getPluginFileName(String pluginName) throws ControllerException {
		PluginInfo info = pluginInfoHash.get(pluginName);

		if (info == null) {
			throw new ControllerException("No configuration for plugin: '"
					+ pluginName + "'");
		}

		return info.fileName;
	}


	/** 
	 * Looks up the class name for a plugin.
	 * 
	 * @param pluginName The name to check the configuration for the actual
	 *        actual class name.
	 * @return The name of the class in the installations plugin directory that
	 *         is defined to correspond to the provided name (which resides in a
	 *         jar file whose name is obtained by a call to 'getPluginFileName').
	 * @throws ControllerException If no class name corresponding to the supplied
	 *         name is found.
	 */
	protected String getPluginClassName(String pluginName) throws ControllerException {
		PluginInfo info = pluginInfoHash.get(pluginName);

		if (info == null) {
			throw new ControllerException("No configuration for plugin: '"
					+ pluginName + "'");
		}

		return info.className;
	}
}


